home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / ISSUE22 / TIMING / HIRESTMR.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1997-03-22  |  15.2 KB  |  707 lines

  1. Unit HiResTmr;
  2.  
  3. {$I LibDef.inc}
  4.  
  5. Interface
  6.  
  7.  
  8. {$IFDef TargetDelphi }
  9.  uses
  10.   WinTypes, WinProcs;
  11. {$ENDIF}
  12.  
  13.  
  14.  
  15.  
  16.  
  17. { This section only valid for 16-bit targets }
  18. { ############################################################## }
  19. {$IFDef Target16Bit }
  20.  
  21.  
  22.  
  23.   { Timer definitions }
  24.   { -------------------------------------------------------- }
  25.  
  26.  
  27.  { Compatible with 32-bit API definitions }
  28.  type
  29.     LONGLONG = Comp;
  30.     PLargeInteger = ^TLargeInteger;
  31.     TLargeInteger = record
  32.       case Integer of
  33.       0: (
  34.         LowPartLowWord,
  35.         LowPartHighWord,
  36.         HighPartLowWord,
  37.         HighPartHighWord : word);
  38.       1: (
  39.         LowPart : Longint;
  40.         HighPart: Longint);
  41.       2: (
  42.         QuadPart: LONGLONG);
  43.     end;
  44.  
  45.  
  46.   procedure QueryPerformanceCounter( var AValue : TLargeInteger );
  47.   { Returns the value of the hi perf counter }
  48.  
  49.   { End Timer definitions }
  50.   { -------------------------------------------------------- }
  51.  
  52.  
  53.  
  54.  
  55.  
  56.   { Windows support routines }
  57.   { -------------------------------------------------------- }
  58.  
  59.   procedure BeginCriticalSection;
  60.   { Advises windows to suspend time-slice }
  61.   procedure EndCriticalSection;
  62.   { Advises windows to released suspended time-slice }
  63.   function  bfunc_GetWindowsInstalledState : boolean;
  64.   { Returns the state of windows installed }
  65.   function  wfunc_GetWindowsInstalledVersion : word;
  66.   { Returns the version of windows. Result is
  67.     HiByte = minor version, LoByte = Major version }
  68.  
  69.   function  GetDeviceEntryPointAddress(
  70.               ADeviceID : word ) : pointer;
  71.   { Returns the entry point for this specified device driver }
  72.  
  73.   { End Windows support routines }
  74.   { -------------------------------------------------------- }
  75.  
  76.  
  77.  
  78. { End section only valid for 16-bit targets }
  79. { ############################################################## }
  80. {$ENDIF }
  81.  
  82.  
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89. { This section valid for all targets }
  90. { ############################################################## }
  91.  
  92.  
  93.   { Timer definitions }
  94.   { -------------------------------------------------------- }
  95.  
  96.   { Definitions relating to the performance counter in use }
  97.   const
  98.  
  99.   { The max value before rollover. Default is -1 but
  100.     MSDOS timer counter rolls over each midnight and will
  101.     reprogram this value }
  102.   r_CounterMaxValue : TLargeInteger = ( QuadPart : -1 );
  103.  
  104.  
  105.   { The resolution which defaults in DOS and Win3 to about 0.8us,
  106.     Delphi 2 32-bit will load this value too, in case
  107.     it changes in the future }
  108.   r_CountsPerSec    : TLargeInteger = ( QuadPart : 1193180);
  109.  
  110.  
  111.   { The number of counter counts taken to call the performance
  112.     counter }
  113.   r_CounterCallOverhead : TLargeInteger = ( QuadPart : 0);
  114.  
  115.  
  116.  
  117.   procedure DelayUS( AValue : longint );
  118.   { Delays this value using the performance counter }
  119.  
  120.   procedure DelayMS( AValue : longint );
  121.   { Delays this value using the performance counter }
  122.  
  123.   procedure DelayS( const AValue : extended );
  124.   { Delays this value using the performance counter }
  125.  
  126.   { End Timer definitions }
  127.   { -------------------------------------------------------- }
  128.  
  129.  
  130.  
  131.   { Windows support routines }
  132.   { -------------------------------------------------------- }
  133.  
  134.   const
  135.    b_WindowsInstalled : boolean = False;
  136.    w_WindowsVersion   : word    = 0;
  137.  
  138.   { End Windows support routines }
  139.   { -------------------------------------------------------- }
  140.  
  141. { End section valid for all targets }
  142. { ############################################################## }
  143.  
  144.  
  145.  
  146.  
  147.  
  148.  
  149.  
  150. Implementation
  151.  
  152.  
  153.  
  154.  
  155. {$IFDef TargetDOSMode }
  156.  uses
  157.   Dos;
  158. {$ENDIF}
  159.  
  160.  
  161.  
  162.  
  163.  
  164.  
  165.  
  166. { Section valid for all targets }
  167. { ############################################################## }
  168.  
  169.   function  GetElapsed(
  170.                const AValue, AStart : comp ) : comp;
  171.   { Returns the duration between AValue and AStart including any rollover }
  172.   begin
  173.     If AValue < AStart then
  174.       { Has rolled over }
  175.        GetElapsed := AValue + (r_CounterMaxValue.QuadPart - AStart)
  176.      else
  177.        GetElapsed := AValue - AStart;
  178.   end;
  179.  
  180.  
  181.  
  182.   procedure WaitForElapsed( const AValue : TLargeInteger);
  183.   { Hangs waiting for counter to reach this value }
  184.   var
  185.    R, r_Start : TLargeInteger;
  186.   begin
  187.    QueryPerformanceCounter( r_Start );
  188.    Repeat
  189.      QueryPerformanceCounter( R );
  190.    until GetElapsed( R.QuadPart, r_Start.QuadPart ) >= AValue.QuadPart;
  191.   end;
  192.  
  193.  
  194.  
  195.  
  196.   procedure CalibratePerformanceCounterOverhead;
  197.   { Calls the performance counter to determine the time overhead }
  198.   var
  199.    I              : word;
  200.    r_Start, r_End : TLargeInteger;
  201.   begin
  202.  
  203.    {$IFNDEF TargetDelphi2}
  204.    BeginCriticalSection;
  205.    {$ENDIF}
  206.  
  207.    QueryPerformanceCounter( r_Start );
  208.  
  209.    For I := 1 to 1000 do
  210.      QueryPerformanceCounter( r_End );
  211.  
  212.    {$IFNDEF TargetDelphi2}
  213.    EndCriticalSection;
  214.    {$ENDIF}
  215.  
  216.    { Get the difference }
  217.    r_CounterCallOverhead.QuadPart := GetElapsed( r_End.QuadPart, r_Start.QuadPart );
  218.    r_CounterCallOverhead.QuadPart := r_CounterCallOverhead.QuadPart / 1000;
  219.   end;
  220.  
  221.  
  222.  
  223.  
  224.   procedure DelayUS( AValue : longint );
  225.   { Delays this value using the performance counter }
  226.   var
  227.     r_Elapsed  : TLargeInteger;
  228.   begin
  229.     r_Elapsed.QuadPart :=
  230.       ((AValue * r_CountsPerSec.QuadPart) * 1e-6)
  231.      -( r_CounterCallOverhead.LowPart * 2);
  232.     If r_Elapsed.QuadPart < 0 then
  233.       r_Elapsed.QuadPart := 0;
  234.     WaitForElapsed( r_Elapsed );
  235.   end;
  236.  
  237.  
  238.  
  239.   procedure DelayMS( AValue : longint );
  240.   { Delays this value using the performance counter }
  241.   var
  242.     r_Elapsed : TLargeInteger;
  243.   begin
  244.     r_Elapsed.QuadPart :=
  245.       ((AValue * r_CountsPerSec.QuadPart) * 1e-3)
  246.      - (r_CounterCallOverhead.QuadPart * 2);
  247.     If r_Elapsed.QuadPart < 0 then
  248.       r_Elapsed.QuadPart := 0;
  249.     WaitForElapsed( r_Elapsed );
  250.   end;
  251.  
  252.  
  253.   procedure DelayS( const AValue : extended );
  254.   { Delays this value using the performance counter }
  255.   var
  256.     r_Elapsed : TLargeInteger;
  257.   begin
  258.     r_Elapsed.QuadPart :=
  259.        (AValue * r_CountsPerSec.QuadPart)
  260.      - (r_CounterCallOverhead.QuadPart * 2);
  261.     If r_Elapsed.QuadPart < 0 then
  262.       r_Elapsed.QuadPart := 0;
  263.     WaitForElapsed( r_Elapsed );
  264.   end;
  265.  
  266.  
  267.  
  268. { End Section valid for all targets }
  269. { ############################################################## }
  270.  
  271.  
  272.  
  273.  
  274.  
  275.  
  276.  
  277.  
  278.  
  279.  
  280.  
  281.  
  282.  
  283.  
  284.  
  285.  
  286.  
  287.  
  288.  
  289.  
  290.  
  291.  
  292.  
  293.  
  294.  
  295.  
  296. { This section only valid for MSDOS targets }
  297. { ############################################################## }
  298. {$IFDef TargetDOSMode }
  299.  
  300.  
  301.  
  302.  
  303.   { MSDOS counter implementation of 'QueryPerformanceCounter' }
  304.   { ----------------------------------------------------------}
  305.  
  306.  
  307.   procedure InitializeTimerCounter;
  308.   { Setup the timer chip to required mode
  309.     Thanks to TurboPower inc for information in OPTIMER.PAS }
  310.   begin
  311.     { mode 2, read/write channel 0}
  312.     Port[$43] := $34;        {00110100b}
  313.     asm
  314.       jmp @1  {delay}
  315.     @1:
  316.     end;
  317.     Port[$40] := $00;        {LSB = 0}
  318.     asm
  319.       jmp @2  {delay}
  320.     @2:
  321.     end;
  322.     Port[$40] := $00;        {MSB = 0}
  323.   end;
  324.  
  325.   procedure RestoreTimerCounter;
  326.   { Restore the timer chip to its normal state
  327.     Thanks to TurboPower inc for information in OPTIMER.PAS }
  328.   begin                      {RestoreTimer}
  329.     {select timer mode 3, read/write channel 0}
  330.     Port[$43] := $36;        {00110110b}
  331.     asm
  332.       jmp @1 {delay}
  333.     @1:
  334.     end;
  335.     Port[$40] := $00;        {LSB = 0}
  336.     asm
  337.       jmp @2 {delay}
  338.     @2:
  339.     end;
  340.     Port[$40] := $00;        {MSB = 0}
  341.   end;
  342.  
  343.  
  344.   procedure QueryMSDOSTimerCounter( Var AValue : TLargeInteger );
  345.   { Returns the value of the hi perf counter.
  346.     Thanks to TurboPower inc for information in OPTIMER.PAS }
  347.   begin
  348.     asm
  349.       cli             {Disable interrupts}
  350.       mov  dx,$20     {Address PIC ocw3}
  351.       mov  al,$0A     {Ask to read irr}
  352.       out  dx,al
  353.       mov  al,$00     {Latch timer 0}
  354.       out  $43,al
  355.       in   al,dx      {Read irr}
  356.       mov  di,ax      {Save it in DI}
  357.       in   al,$40     {Counter --> bx}
  358.       mov  bl,al      {LSB in BL}
  359.       in   al,$40
  360.       mov  bh,al      {MSB in BH}
  361.       not  bx         {Need ascending counter}
  362.       in   al,$21     {Read PIC imr}
  363.       mov  si,ax      {Save it in SI}
  364.       mov  al,$0FF    {Mask all interrupts}
  365.       out  $21,al
  366.       mov  ax,$40     { delay }
  367.  
  368.       mov  ax,$40     {read low word of time}
  369.       mov  es,ax      {from BIOS data area}
  370.       mov  dx,es:[$6C]
  371.       mov  cx,es:[$6E]{result now as CX:DX:BX}
  372.  
  373.       mov  ax,si      {Restore imr from SI}
  374.       out  $21,al
  375.       sti             {Enable interrupts}
  376.       mov  ax,di      {Retrieve old irr}
  377.       test al,$01     {Counter hit 0?}
  378.       jz   @done      {Jump if not}
  379.       cmp  bx,$FF     {Counter > $FF?}
  380.       ja   @done      {Done if so}
  381.       add  dx,1       {Else count int req.}
  382.       adc  cx,0       {ripple carry}
  383.  
  384.  
  385.    @done:
  386.       les di, AValue
  387.       mov es:[di],  bx  {lsw}
  388.       mov es:[di+2],dx
  389.       mov es:[di+4],cx
  390.       mov ax,0
  391.       mov es:[di+6],ax  {msw}
  392.   end;
  393.   end;
  394.  
  395.   var
  396.    SaveExitProc : pointer;
  397.  
  398.  
  399.   {$F+}
  400.   procedure OurExitProc;
  401.   { Restore timer to its original state}
  402.   begin
  403.     ExitProc := SaveExitProc;
  404.     RestoreTimerCounter;
  405.   end;
  406.   {$F-}
  407.  
  408.   procedure InitialiseMSDOSTimerCounter;
  409.   { Called at start to setup timer }
  410.   begin
  411.     {set up our exit handler}
  412.     SaveExitProc := ExitProc;
  413.     ExitProc := @OurExitProc;
  414.  
  415.     {reprogram the timer chip}
  416.     InitializeTimerCounter;
  417.  
  418.     { Set the counter max value }
  419.     With r_CounterMaxValue do
  420.       begin
  421.       LowPart  := $00B0FFFF;
  422.       HighPart := $18;
  423.       end;
  424.   end;
  425.  
  426.  
  427.   { End MSDOS counter implementation of 'QueryPerformanceCounter' }
  428.   { ----------------------------------------------------------}
  429.  
  430.  
  431. {$ENDIF}
  432. { End section only valid for MSDOS targets }
  433. { ############################################################## }
  434.  
  435.  
  436.  
  437.  
  438.  
  439.  
  440.  
  441.  
  442.  
  443.  
  444.  
  445.  
  446.  
  447.  
  448.  
  449.  
  450.  
  451.  
  452.  
  453.  
  454.  
  455. { This section only valid for 16-bit targets }
  456. { ############################################################## }
  457. {$IFDef Target16Bit }
  458.  
  459.  
  460.  
  461.   { Windows support routines }
  462.   { ----------------------------------------------------------}
  463.  
  464.    procedure BeginCriticalSection; assembler;
  465.    { Advises windows to suspend time-slice }
  466.    asm
  467.      mov     ax, 1681h   { Begin critical section      }
  468.      int     2Fh         { multiplex interrupt         }
  469.    end;
  470.  
  471.    procedure EndCriticalSection; assembler;
  472.    { Advises windows to released suspended time-slice }
  473.    asm
  474.      mov     ax, 1682h   { End critical section        }
  475.      int     2Fh         { multiplex interrupt         }
  476.    end;
  477.  
  478.  
  479.    function bfunc_GetWindowsInstalledState : boolean; assembler;
  480.    { Returns the state of windows installed }
  481.    asm
  482.      mov     ax, 1600h   { Get Windows Installed State }
  483.      int     2Fh         { multiplex interrupt         }
  484.      and     al, 7Fh
  485.      jz      @No         { Windows not running         }
  486.      mov al, $FF
  487.    @no:
  488.    end;
  489.  
  490.    function wfunc_GetWindowsInstalledVersion : word; assembler;
  491.    { Returns the version of windows. Result is
  492.      HiByte = minor version, LoByte = Major version }
  493.    asm
  494.      mov     ax, 1600h   { Get Windows Installed State  }
  495.      int     2Fh         { multiplex interrupt          }
  496.      and     al, 7Fh
  497.      jnz     @IsWindows  { Windows running, AX has vers }
  498.      mov ax, 0
  499.    @IsWindows:
  500.    end;
  501.  
  502.  
  503.    function GetDeviceEntryPointAddress(
  504.                ADeviceID : word ) : pointer;
  505.    { Returns the entry point for this specified device driver }
  506.    begin
  507.      If not b_WindowsInstalled then
  508.        GetDeviceEntryPointAddress := nil
  509.       else
  510.        asm
  511.          mov     bx, ADeviceID  { Device identifier }
  512.          mov     ax, 1684h      { Get Device Entry Point Address }
  513.          int     2Fh            { multiplex interrupt }
  514.          mov word ptr @Result,    di
  515.          mov word ptr @Result+2,  es
  516.        end;
  517.    end;
  518.  
  519.  
  520.    { End Windows support routines }
  521.    { -------------------------------------------------------- }
  522.  
  523.  
  524.  
  525.  
  526.  
  527.  
  528.  
  529.  
  530.  
  531.  
  532.  
  533.  
  534.  
  535.   { Windows-hosted routines for timing - 16-bit only }
  536.   { ----------------------------------------------------------}
  537.  
  538.  
  539.   function GetVTDDeviceEntryPointAddress : pointer;
  540.   { Returns the entry point for the virtual timer device
  541.     driver }
  542.   begin
  543.      GetVTDDeviceEntryPointAddress :=
  544.         GetDeviceEntryPointAddress( 5 { VTD identifier } );
  545.   end;
  546.  
  547.  
  548.   const
  549.    VTDAddress : pointer = nil;
  550.  
  551.  
  552.   procedure QueryWindowsVTDCounter( var AValue : TLargeInteger );
  553.   { Returns the value of the hi perf counter }
  554.   begin
  555.      If VTDAddress = nil then
  556.        VTDAddress := GetVTDDeviceEntryPointAddress;
  557.      If VTDAddress = nil then
  558.        RunError; {No VTD installed - needs windows}
  559.      asm
  560.        mov ax,$100
  561.        call VTDAddress
  562.        db $66, $50   {push eax}
  563.        db $58        {pop  ax}
  564.        db $5B        {pop  bx}
  565.        db $66, $52   {push edx}
  566.        db $59        {pop  cx}
  567.        db $5A        {pop  dx}
  568.        les di, AValue
  569.        mov es:[di+0],   ax {w0 lsw}
  570.        mov es:[di+2],   bx {w1 }
  571.        mov es:[di+4],   cx {w2 }
  572.        mov es:[di+6],   dx {w3 msw}
  573.      end;
  574.   end;
  575.  
  576.  
  577.   procedure QueryPerformanceCounter( var AValue : TLargeInteger );
  578.   { Returns the value of the performance counter }
  579.   begin
  580.  
  581.     {$IFDEF TargetDOSMode}
  582.     If b_WindowsInstalled then
  583.      QueryWindowsVTDCounter( AValue )
  584.       else
  585.      QueryMSDOSTimerCounter( AValue );
  586.     {$ENDIF}
  587.  
  588.     {$IFDEF TargetDelphi1}
  589.     QueryWindowsVTDCounter( AValue );
  590.     {$ENDIF}
  591.  
  592.   end;
  593.  
  594.   { End windows-hosted routines for timing - 16-bit only }
  595.   { ----------------------------------------------------------}
  596.  
  597.  
  598.  
  599.  
  600.  
  601.   { General 16-bit routines }
  602.   { ----------------------------------------------------------}
  603.  
  604.  
  605.   procedure InitialisationTasks;
  606.   begin
  607.     { Get details about any Windows running }
  608.     b_WindowsInstalled := bfunc_GetWindowsInstalledState;
  609.     w_WindowsVersion   := wfunc_GetWindowsInstalledVersion;
  610.  
  611.     {$IFDEF TargetDosMode}
  612.     If not b_WindowsInstalled then
  613.        InitialiseMSDOSTimerCounter;
  614.     {$ENDIF}
  615.     CalibratePerformanceCounterOverhead;
  616.   end;
  617.  
  618.  
  619.   { End General 16-bit routines }
  620.   { ----------------------------------------------------------}
  621.  
  622.  
  623.  
  624. {$ENDIF}
  625. { End section only valid for 16-bit mode targets }
  626. { ############################################################## }
  627.  
  628.  
  629.  
  630.  
  631.  
  632.  
  633.  
  634. { This section only valid for Delphi 2 32-bit target }
  635. { ############################################################## }
  636. {$IFDef TargetDelphi2 }
  637.  
  638.  
  639.   procedure InitialisationTasks;
  640.   var
  641.      OSVersionInfo: TOSVersionInfo;
  642.    begin
  643.      b_WindowsInstalled := True;
  644.      OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
  645.      if GetVersionEx(OSVersionInfo) then
  646.       With OSVersionInfo do
  647.         w_WindowsVersion   := (dwMinorVersion shl 8) or (dwMajorVersion and $FF)
  648.        else
  649.         w_WindowsVersion   := 0;
  650.  
  651.     QueryPerformanceFrequency( r_CountsPerSec );
  652.     CalibratePerformanceCounterOverhead;
  653.   end;
  654.  
  655.  
  656.  
  657.  
  658.  
  659.  
  660.  
  661. {$ENDIF}
  662. { End section only valid for Delphi 2 32-bit target }
  663. { ############################################################## }
  664.  
  665.  
  666.  
  667.  
  668.  
  669.  
  670.  
  671.  
  672.  
  673.  
  674.  
  675.  
  676.  
  677.  
  678.  
  679.  
  680.  
  681.  
  682.  
  683.  
  684.  
  685.  
  686.  
  687.  
  688.  
  689.  
  690.  
  691.  
  692.  
  693.  
  694.  
  695.  
  696.  
  697.  
  698.  
  699.  
  700.  
  701.  
  702.  
  703.  
  704. begin
  705.   InitialisationTasks;
  706. end.
  707.